local base = _G
local string = base.string
local math = base.math
module("gui")

InputG = {}
InputGMT = { __index = InputG }

base.setmetatable(InputG, { __index = ObjectG })

InputG.create = function(x, y, width, text)
  local styles = get_styles('padding', 'font', 'cursor_blink')
  local colors = get_colors('font', 'font_highlight', 'highlight', 'border', 'background')
  local width = width or 100
  local height = styles.font:get_height() + styles.padding*2

  local self = Object(x, y, width, height, InputGMT)

  self.styles = styles
  self.colors = colors

  self.text = text or ""
  -- cursor position
  self.cursor = 0
  self.blink = 0
  -- selected number of characters (could be negative)
  self.selection = 0
  -- visible text offset
  self.offset = 0

  return self
end

InputG.destroy = function(self)
  self.text = nil
  self.cursor = nil
  self.blink = nil
  self.selection = nil
  self.offset = nil
  
  self.styles = nil
  self.colors = nil
  ObjectG.destroy(self)
end

-- converts string index to position in pixels
InputG.get_index_position = function(self, i)
  local sz = string.sub(self.text, self.offset + 1, i)
  return self.styles.font:get_width(sz) + self.styles.padding
end

-- converts position in pixels to string index
InputG.get_index = function(self, x)
  local sz = string.sub(self.text, self.offset + 1)
  local i = self.styles.font:get_index(sz, x - self.styles.padding)
  return i + self.offset
end

-- set the selection size (could be negative)
InputG.set_selection = function(self, s)
  -- clamp selection value
  local len = string.len(self.text)
  s = math.max(s, - self.cursor)
  s = math.min(s, len - self.cursor)
  -- assign
  self.selection = s
end

-- returns the start and end selection indices
InputG.get_selection_index = function(self)
  local s = self.cursor
  local e = self.cursor + self.selection
  if s > e then
    s, e = e, s
  end
  return s, e
end

-- returns the selection string
InputG.get_selection = function(self)
  local s, e = self:get_selection_index()
  return string.sub(self.text, s + 1, e)
end

InputG.select_all = function(self)
  local len = string.len(self.text)
  self:set_cursor(0)
  self:set_selection(len)
end

-- set cursor position
InputG.set_cursor = function(self, index)
  -- clamp index value
  local len = string.len(self.text)
  index = math.max(index, 0)
  index = math.min(index, len)
  self.cursor = index
  self.blink = 0 
end

-- update the offset
InputG.update_offset = function(self)
  --shift offset to keep the cursor on-screen
  local c = self.cursor + self.selection
  self.offset = math.min(self.offset, c)
  
  local pos = self:get_index_position(self.cursor + self.selection)
  while pos > self.width - self.styles.padding do
    self.offset = self.offset + 1
    pos = self:get_index_position(self.cursor + self.selection)
  end

  local len = string.len(self.text)
  local epos = self:get_index_position(len)
  while self.offset > 0 do
    self.offset = self.offset - 1
    epos = self:get_index_position(len)
    if epos >= self.width - self.styles.padding then
      self.offset = self.offset + 1
      break
    end
  end
end

-- write text to the InputG field
InputG.write = function(self, c)
  -- overwrite old selection
  local s, e = self:get_selection_index()
  local pre = string.sub(self.text, 1, s)
  local post = string.sub(self.text, e + 1)
  self.text = pre .. c .. post
  -- update the cursor
  self.cursor = s + string.len(c)
  self.selection = 0
  self.blink = 0
  self:update_offset()
  self:on_change(self.text)
end

-- handle key commands
InputG.key_command = function(self, key, ctrl, shift)
  if key == base.KEY_BACK or key == base.KEY_DEL then
    if self.selection == 0 then
      local dir = 1
      if key == base.KEY_BACK then
        dir = -dir
      end
      if ctrl == true then
        -- delete whole word
        dir = string.find_break(self.text, self.cursor + 1, dir < 0)
      end
      self:set_selection(self.selection + dir)
    end
    self:write('')
  elseif key == base.KEY_C or key == base.KEY_X then
    if ctrl == true then
      local sz = self:get_selection()
      if string.len(sz) > 0 then
        set_cilpboard(sz)
        if key == base.KEY_X then
          self:write('')
        end
      end
    end
  elseif key == base.KEY_V then
    if ctrl == true then
      local sz = get_clipboard()
      if sz then
        self:write(sz)
      end
    end
  elseif key == base.KEY_A then
    if ctrl == true then
      -- select all
      self:select_all()
    end
    self:update_offset()
  elseif key == base.KEY_Z then
    if ctrl == true then
      -- todo: undo
    end
    self:update_offset()
  elseif key == base.KEY_LEFT or key == base.KEY_UP or key == base.KEY_RIGHT or key == base.KEY_DOWN then
    local dir = 1
    if key == base.KEY_LEFT or key == base.KEY_UP then
      dir = -dir
    end
    if ctrl == true then
      -- skip whole word
      dir = string.find_break(self.text, self.cursor + self.selection + 1, dir < 0)
    end
    if shift == true then
      -- shift sel
      self:set_selection(self.selection + dir)
    else
      -- move cursor
      if self.selection ~= 0 then
        self.cursor = self.cursor + self.selection
      end
      self.selection = 0
      self:set_cursor(self.cursor + dir)
    end
    self:update_offset()
  elseif key == base.KEY_HOME or key == base.KEY_END then
    local dir = -self.cursor
    if key == base.KEY_END then
      dir = string.len(self.text) - self.cursor
    end
    if shift == true then
      self:set_selection(dir)
    else
      self.selection = 0
      self:set_cursor(self.cursor + dir)
    end
    self:update_offset()
  elseif key == base.KEY_ENTER then
    self:on_select(self.text)
  end
end

-- handle mouse input
InputG.mouse_press = function(self, button, x, y)
  local out = string.sub(self.text, self.offset + 1)
  out = self.styles.font:clamp(out, self.width - self.styles.padding)
  local c = self:get_index(x)
  c = math.min(c, string.len(out) + self.offset)
  self.cursor = c
  self.selection = 0
  self.blink = 0
end

InputG.mouse_release = function(self, button, x, y)

end

InputG.dragging = function(self, button, x, y)
  self.selection = self:get_index(x) - self.cursor
  self:update_offset()
end

InputG.double_click = function(self, button, x, y)
  self:select_all()
end

InputG.redraw = function(self, dt)
  local c = self.sprite.canvas
  c:clear()
  
  local alpha = 0.5
  if self.is_focused == true then
    alpha = 1
  elseif self.is_hovered == true then
    alpha = 0.75
  end

  -- draw background rectangle
  local l, t, r, b = 0, -self.height, self.width, 0
  c:rect(l - 1, t - 1, r + 1, b + 1)
  c:set_fill_style(self.colors.border, alpha)
  c:fill()
  c:rect(l, t, r, b)
  c:set_fill_style(self.colors.background, 1)
  c:fill()

  -- write the input text
  local out = string.sub(self.text, self.offset + 1)
  out = self.styles.font:clamp(out, self.width - self.styles.padding)
  -- baseline
  local bs =(self.height - self.styles.font:get_size())/2
  bs = bs - self.height
  local pad = self.styles.padding
  local y1 = pad - self.height
  local y2 = y1 + self.height - pad*2
  c:set_font(self.styles.font, self.colors.font, alpha)
  c:move_to(pad, bs)
  c:write(out)

  if self.selection == 0 then
    if self.is_focused == true then
      self.blink = self.blink + dt
      local t = math.floor(self.blink/self.styles.cursor_blink)
      if t % 2 == 0 then
        -- draw the cursor
        local x = self:get_index_position(self.cursor)
        c:move_to(x, y1)
        c:line_to(x, y2)
        c:set_line_style(1, self.colors.font, alpha)
        c:stroke()
      end
    end
  else
    -- draw the highlighted selection
    local s, e = self:get_selection_index()
    s = math.max(s, self.offset)
    e = math.min(e, self.offset + string.len(out))
    local x1 = self:get_index_position(s)
    local x2 = self:get_index_position(e)
    c:rect(x1, y1, x2, y2)
    c:set_fill_style(self.colors.highlight, alpha)
    c:fill()
    c:move_to(x1, bs)
    c:set_font(self.styles.font, self.colors.font_highlight, 1)
    c:write(string.sub(self.text, s + 1, e))
  end
end

InputG.update = function(self, dt)
  -- todo: don't redraw all the time
  self:redraw(dt)
end

InputG.on_select = function(self, text)
  
end

InputG.on_change = function(self, text)
  
end

Input = InputG.create